package node

import (
	"fmt"
	"strings"
	"sync"

	"miego/klog"
	"miego/xmap"
)

type KNodeManager struct {
	//
	// ALL nodes
	//
	nodes []*KNode

	// nodeTypeA <> downstreamNodes
	// dataReady(n, data) => lbNode := nodeTopoType[n.Type]
	//
	// XXX: nm.NodeAdd(node, NodeType, DataFormat, DataType)
	// "NodeType*hint" == (NodeA, NodeB, ..., NodeX)
	followMap map[uint64][]*KNode
}

var cacheLock = &sync.Mutex{}

var cacheNexts map[uint64][]*KNode

func init() {
	cacheNexts = make(map[uint64][]*KNode)
}

// 把一个节点纳入管理。
func (nm *KNodeManager) NodeAdd(node *KNode) {
	node.TypeId = NpAdd(node.Type)
	node.nm = nm
	nm.nodes = append(nm.nodes, node)

	s := node.Follows
	if s == "" {
		return
	}

	var uhint, udatafmt, unode uint64
	for _, m := range strings.Split(s, ";") {
		segs := strings.Split(m, "/")
		if len(segs) != 3 {
			klog.E("Bad Follows format '%s' for node '%s'", m, node.Name)
			continue
		}

		xhint, xdatafmt, xnode := segs[0], segs[1], segs[2]

		if xhint == "*" {
			uhint = HT_ANY
		} else {
			uhint = NpNum(xhint)
		}

		if xdatafmt == "*" {
			udatafmt = DF_ANY
		} else {
			udatafmt = NpNum(xdatafmt)
		}

		if xnode == "*" {
			unode = NT_ANY
		} else {
			unode = NpNum(xnode)
		}

		nm.LinkAfter(node, unode, udatafmt, uhint)
	}
}

func catchError() {
	if err := recover(); err != nil {
		klog.F("----------")
		klog.E("%s\n", fmt.Sprint(err))
		klog.F("----------")
	}
}

func (nm *KNodeManager) LinkAfter(node *KNode, nodeType uint64, datafmt uint64, hint uint64) {
	hashkey := nodeType * datafmt * hint

	subNodes, ok := nm.followMap[hashkey]
	if !ok {
		subNodes = make([]*KNode, 0)
	}
	subNodes = append(subNodes, node)
	nm.followMap[hashkey] = subNodes
}

func (nm *KNodeManager) startNodes() {
	for _, n := range nm.nodes {
		if n.OnStart != nil {
			n.OnStart(nm, n)
		}
	}
}

// endPipeline: The pipeline at this node and this data.
func (nm *KNodeManager) endPipeline(node *KNode, data interface{}, datafmt uint64, hint uint64) {
	// TODO: Save the frame to database.
}

// Create new frame base on @f and send @data to @nodeDst
//
// @f: Parent frame
//
// XXX: Caller MUST ensure the nodeDst.Processor exists.
func (nm *KNodeManager) callNode(caller *KCallFrame, nodeDst *KNode, data interface{}, datafmt uint64, hint uint64) {
	// defer catchError()

	// 1: Create a new frame and save the data and node to it
	newframe := NewCallFrame(caller, nodeDst, data, datafmt, hint)

	// 2: Call nodeDst.Processor with new frame
	if nodeDst.Processor != nil {
		data, datafmt, hint = nodeDst.Processor(newframe)
	}

	// 3. Push to next stage.
	nm.sendtoSubs(newframe, nodeDst, data, datafmt, hint)
}

// XXX: Directly send data to node
//
// @caller: current(caller's) call frame
// @nodeDst: node who will process the data
// @data: data generated by node
func (nm *KNodeManager) SendtoNode(caller *KCallFrame, nodeDst *KNode, data interface{}, datafmt uint64, hint uint64) {
	go nm.callNode(caller, nodeDst, data, datafmt, hint)
}

func (nm *KNodeManager) getNexts(node *KNode, datafmt uint64, hint uint64) []*KNode {
	ohashkey := node.TypeId * datafmt * hint
	if nexts, ok := cacheNexts[ohashkey]; ok {
		return nexts
	}

	var hashkey uint64
	var nexts []*KNode

	ntList := []uint64{node.TypeId, NT_ANY}
	dfList := []uint64{datafmt, DF_ANY}
	htList := []uint64{hint, HT_ANY}

	for _, nt := range ntList {
		for _, df := range dfList {
			for _, ht := range htList {
				hashkey = nt * df * ht
				if subNodes, ok := nm.followMap[hashkey]; ok {
					nexts = append(nexts, subNodes...)
				}
			}
		}
	}

	cacheLock.Lock()
	cacheNexts[ohashkey] = nexts
	cacheLock.Unlock()

	return nexts
}

// XXX: Send data to down stream node
//
// @caller: current(caller's) call frame
// @nodeDst: node who generate the data
// @data: data generated by node
func (nm *KNodeManager) sendtoSubs(caller *KCallFrame, nodeSrc *KNode, data interface{}, datafmt uint64, hint uint64) {

	nextNodes := nm.getNexts(nodeSrc, datafmt, hint)
	if nextNodes == nil {
		// XXX: end this pipeline
		nm.endPipeline(nodeSrc, data, datafmt, hint)
	}

	for _, nodeNext := range nextNodes {
		if nodeNext.Processor != nil && nodeNext != nodeSrc {
			// klog.W("%v => %v", nodeSrc.Name, nodeNext.Name)
			nm.callNode(caller, nodeNext, data, datafmt, hint)
		}
	}
}

func (nm *KNodeManager) Run() {
	NpDump()

	cp := klog.Color

	fmtNode := func(n *KNode) xmap.Map {
		return xmap.Make(
			"     Name", n.Name,
			"     Desc", n.Desc,
			"     Type", n.Type,
			"   TypeId", n.TypeId,
			"Processor", n.Processor,
			"  OnStart", n.OnStart,
		)
	}

	for hashkey, arr := range nm.followMap {
		for i, n := range arr {
			m := fmtNode(n)
			x := strings.Join(NpSplitS(hashkey), " ")
			klog.Dump(m, cp("33;1", "(%s) / %d\n", x, i))
		}
	}

	for i, n := range nm.nodes {
		m := fmtNode(n)
		klog.Dump(m, cp("93;1", "NODE %d\n", i))
	}

	//
	// buildTopo the topology
	//
	nm.startNodes()

	for {
		select {}
	}
}

var NodeManager *KNodeManager

func init() {
	NodeManager = new(KNodeManager)
	NodeManager.followMap = make(map[uint64][]*KNode)
}
